//glad.h必须在glfw3.h之前引入
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "wrapper/checkErr.h"
#include "application/application.h"

GLuint vao, program;

static void onSize(int width, int height) {
	std::cout << "on size" << std::endl;
}
static void onKey(int key, int action, int mods) {
	if (action) {
		std::cout << "按下了:" << key << ",mods = " << mods << std::endl;
	}
	else {
		std::cout << "弹起了:" << key << ",mods = " << mods << std::endl;
	}
}

//vbo与多属性数据存储
//- SingleBuffer：每一个属性放在单独的vbo中
void prepareSingleBuffer() {
	//顶点位置信息
	float positions[] = {
		-0.5f,-0.5f,0.0f,
		0.5f,-0.5f,0.0f,
		0.0f,0.5f,0.0f,
	};
	//颜色属性信息
	float colors[] = {
		1.0f,0.0f,0.0f,
		0.0f,1.0f,0.0f,
		0.0f,0.0f,0.1f,
	};
	//创建位置和颜色的vbo
	GLuint posVBO = 0, colorVBO = 0;
	GL_CALL(glGenBuffers(1, &posVBO), "创建位置vbo");
	GL_CALL(glGenBuffers(1, &colorVBO), "创建颜色vbo");
	//分别给两个vbo填充数据
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, posVBO), "绑定位置VBO");
	GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW), "填充位置信息");
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, colorVBO), "绑定颜色VBO");
	GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW), "填充颜色信息");

	//创建vao
	GL_CALL(glGenVertexArrays(1, &vao),"创建vao");
	GL_CALL(glBindVertexArray(vao),"绑定vao");
	
	//分别将位置和颜色信息属性的描述加入vao中
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, posVBO),"描述位置信息：只有绑定了posVBO，下面的属性描述才会与此vbo相关联");
	GL_CALL(glEnableVertexAttribArray(0),"激活0号位置");
	/*
	GLuint index:位置属性放0号位置
	GLint size:一个属性三个数字
	GLenum type:属性数字是float
	GLboolean normalized:是否归一化
	GLsize stride:顶点数据跨度步长
	const void* pointer:顶点数据内部偏移
	*/
	GL_CALL(glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3 * sizeof(float),(void *)0),"关联位置描述信息");

	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, colorVBO),"绑定颜色信息");
	GL_CALL(glEnableVertexAttribArray(0), "激活1号位置");
	GL_CALL(glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0), "关联颜色描述信息");

	GL_CALL(glBindVertexArray(0),"将当前vao解绑");

}
//-InterleavedBuffer：将所有属性存储在一个vbo中，并且数据是交叉的
void prepareInterleavedBuffer() {
	float vertices[] = {
		-0.5f,-0.5f,0.0f,	1.0f,0.0f,0.0f,
		0.5f,-0.5f,0.0f,	0.0f,1.0f,0.0f,
		0.0f,0.5f,0.0f,		0.0f,0.0f,0.1f,
	};
	GLuint vbo = 0;
	GL_CALL(glGenBuffers(1, &vbo),"创建vbo");
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo), "绑定vbo");
	GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW), "填充数据");

	GL_CALL(glGenVertexArrays(1, &vao), "创建vao");
	GL_CALL(glBindVertexArray(vao), "绑定vao"); 
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vbo), "绑定vbo");
	GL_CALL(glEnableVertexAttribArray(0), "启用0号位置");
	GL_CALL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0), "关联位置描述信息");
	GL_CALL(glBindVertexArray(0), "将当前vao解绑");
}

void prepareVAOForGLTriangles() {
	float positions[] = {
		-0.5f,-0.5f,0.0f,
		0.5f, -0.5f,0.0f,
		0.0f, 0.5f, 0.0f,
		0.5f, 0.5f, 0.0f,
		0.8f, 0.8f, 0.0f,
		0.8f, 0.0f, 0.0f,
	};
	GLuint vboPos = 0;
	GL_CALL(glGenBuffers(1, &vboPos), "创建vbo");
	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vboPos), "绑定vbo");
	GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW), "填充数据");

	GL_CALL(glGenVertexArrays(1, &vao), "创建vao");
	GL_CALL(glBindVertexArray(vao), "绑定vao");

	GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, vboPos), "描述位置信息：只有绑定了posVBO，下面的属性描述才会与此vbo相关联");
	GL_CALL(glEnableVertexAttribArray(0), "激活0号位置");
	GL_CALL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0), "关联位置描述信息");
	GL_CALL(glBindVertexArray(0), "将当前vao解绑");

}

void prepareShader() {
	//1.创建shader源代码
	std::string vertexShaderSourceStr = R"(
		#version 460 core
		layout (location=0) in vec3 aPos;
		void main(){
			gl_Position = vec4(aPos.x,aPos.y,aPos.z,1.0);
		}
	)";
	const char* vertexShaderSource = vertexShaderSourceStr.data();
	std::string fragmentShaderSourceStr = R"(
		#version 330 core
		out vec4 FragColor;
		void main(){
			FragColor = vec4(1.0f,0.5f,0.2f,1.0f);
		} 
	)";
	const char* fragmentShaderSource = fragmentShaderSourceStr.data();

	//2.创建shader程序（vs，fs）
	GLuint vertex, fragment;
	vertex = glCreateShader(GL_VERTEX_SHADER);
	fragment = glCreateShader(GL_FRAGMENT_SHADER);

	//3.为shader程序输入shader代码
	glShaderSource(vertex,1, &vertexShaderSource,NULL);
	glShaderSource(fragment,1, &fragmentShaderSource,NULL);

	int success = 0;
	char infoLog[1024];
	//4.执行shader代码编译
	glCompileShader(vertex);
	//获取编译结果
	glGetShaderiv(vertex,GL_COMPILE_STATUS,&success);
	if (!success) {
		glGetShaderInfoLog(vertex,1024,NULL,infoLog);
		std::cout << "vertex shader compile err : " << infoLog << std::endl;
	}
	glCompileShader(fragment);
	glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
	if (!success) {
		glGetShaderInfoLog(fragment, 1024, NULL, infoLog);
		std::cout << "fragment shader compile err : " << infoLog << std::endl;
	}

	//5.创建一个program的壳子
	program = glCreateProgram();

	//6.将vs和fs编译好的结果放到program这个壳子中
	glAttachShader(program, vertex);
	glAttachShader(program, fragment);

	//7.执行program的链接操作，形成最终可执行的shader程序
	glLinkProgram(program);
	//检查链接错误
	glGetProgramiv(program, GL_LINK_STATUS, &success);
	if (!success) {
		glGetProgramInfoLog(program, 1024, NULL, infoLog);
		std::cout << "program link err : " << infoLog << std::endl;
	}

	//8.清理
	glDeleteShader(vertex);
	glDeleteShader(fragment);
}

void render() {
	GL_CALL(glClear(GL_COLOR_BUFFER_BIT), "执行画布清理工作");
	//1.绑定当前的program
	glUseProgram(program);
	//2.绑定当前的vao
	glBindVertexArray(vao);
	//3.发出绘制指令
	glDrawArrays(GL_TRIANGLES,0,3);
	//glDrawArrays(GL_TRIANGLES,1,3);
	//glDrawArrays(GL_TRIANGLES,0,4);
	//glDrawArrays(GL_TRIANGLE_STRIP,0,6);
	//glDrawArrays(GL_TRIANGLE_FAN,0,6);
	//glDrawArrays(GL_LINES,0,6);
	//glDrawArrays(GL_LINE_STRIP,0,6);
	//GL_TRIANGLES表示三个点绘制一次
}


int main()
{
	if (!app->init()) {
		std::cout << "init fail " << std::endl;
		return -1;
	}

	app->setResizeCallback(onSize);
	app->setKeyBoardCallback(onKey);

	GL_CALL(glViewport(0, 0, 800, 600), "设置视口大小");
	GL_CALL(glClearColor(0.2f, 0.3f, 0.3f, 1.0f), "设置画布清理颜色");

	prepareShader();
	//prepareInterleavedBuffer();
	prepareSingleBuffer();
	//prepareVAOForGLTriangles();

	while (app->update()) {
		render();
	}
	 
	if (!app->destroy()) {
		std::cout << "destroy fail" << std::endl;
	}
	return 0;
}